package cn.lijiajia3515.cairo.auth.framework.security.rememberme;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class RedisPersistentTokenRepository implements PersistentTokenRepository {


	/**
	 * 默认保存2周时间
	 */
	public final Duration expire = Duration.ofDays(14);

	/* hash key */
	public static final String TOKEN_VALUE_KEY = "token_value";

	public static final String USERNAME_KEY = "username";

	public static final String DATE_KEY = "date";

	@Setter
	private String namespace = "";

	/* redis key */
	/**
	 * token前缀
	 */
	public final String TOKEN_PREFIX = "remember_me:token";

	/**
	 * tokens前缀
	 */
	public final String TOKENS_PREFIX = "remember_me:tokens";


	private final RedisTemplate<String, Object> redisTemplate;

	public RedisPersistentTokenRepository(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	@Override
	public void createNewToken(PersistentRememberMeToken token) {
		HashMap<Object, Object> value = new HashMap<>() {{
			put(USERNAME_KEY, token.getUsername());
			put(TOKEN_VALUE_KEY, token.getTokenValue());
			put(DATE_KEY, token.getDate());
		}};

		String seriesKey = generateTokenKey(token.getSeries());
		redisTemplate.opsForHash().putAll(seriesKey, value);
		redisTemplate.expire(seriesKey, expire);
		String usernameKey = generateTokensKey(token.getUsername());
		redisTemplate.opsForSet().add(usernameKey, token.getSeries());
		redisTemplate.expire(usernameKey, expire);
	}

	@Override
	public void updateToken(String series, String tokenValue, Date lastUsed) {
		HashMap<Object, Object> value = new HashMap<>() {{
			put(TOKEN_VALUE_KEY, tokenValue);
			put(DATE_KEY, lastUsed);
		}};

		String seriesKey = generateTokenKey(series);
		redisTemplate.opsForHash().putAll(seriesKey, value);
		redisTemplate.expire(seriesKey, expire);
	}

	@Override
	public PersistentRememberMeToken getTokenForSeries(String seriesId) {
		String key = generateTokenKey(seriesId);
		List<Object> value = redisTemplate.opsForHash().multiGet(key, List.of(USERNAME_KEY, TOKEN_VALUE_KEY, DATE_KEY));
		if (value.size() != 3 || value.stream().anyMatch(Objects::isNull)) {
			return null;
		}
		return new PersistentRememberMeToken((String) value.get(0), seriesId, (String) value.get(1), (Date) value.get(2));
	}

	@Override
	public void removeUserTokens(String username) {
		String usernameKey = generateTokensKey(username);
		Optional.ofNullable(redisTemplate.opsForSet().size(usernameKey))
			.ifPresentOrElse(size -> {
				List<String> seriesKeys = Optional.ofNullable(redisTemplate.opsForSet().pop(usernameKey, size))
					.orElse(Collections.emptyList()).stream()
					.filter(Objects::nonNull)
					.map(x -> generateTokenKey(x.toString()))
					.collect(Collectors.toList());
				List<String> deleteKeys = Stream.of(seriesKeys, Collections.singletonList(usernameKey)).flatMap(Collection::stream).collect(Collectors.toList());
				redisTemplate.delete(deleteKeys);
			}, () -> log.debug("remove persistent token : {}", username));
	}

	public String generateTokenKey(String series) {
		return String.format("%s:%s:%s", namespace, TOKEN_PREFIX, series);
	}

	public String generateTokensKey(String username) {
		return String.format("%s:%s:%s", namespace, TOKENS_PREFIX, username);
	}
}
